module render.filtersampler;

import render.abstractrenderer;
import canvas.abstractcanvas;
import canvas.floatcanvas;
import utils.vector2;
import utils.color;
import std.math;
import std.stdio;

enum DefaultFilter {
    NONE,
    BOX,
    TRIANGLE,
    GAUSS,
    MITCHELL,
    TEST,
}

public class FilterFunction {
    abstract float filter(float n);
    abstract char[] name();
    float filter(float x, float y) {
        return filter(x)*filter(y);
    }
}

public class FilterSampler {
    private float[] mFilterKernel;
    private int mFilterWidth, mFilterHeight;
    private AbstractCanvas mSrcImage;
    private AbstractCanvas mDstImage;
    private char[] mFilterName;

    private Vector2f mRendererSize;
    private Vector2i mConcreteSize;

    private Vector2f mP1;
    private Vector2f mP2;
    private Vector2f mFuzz;

    public this() {
        mFilterKernel = null;
        setDefaultFilter(DefaultFilter.NONE, 0);
    }

    public void initRender(Vector2f pixelSize, Vector2f validFrom,
        Vector2f validTo)
    {
/*        mRendererSize = mRenderer.renderSize;
        mP1 = validFrom;
        mP2 = validTo;
        mConcreteSize = Vector2i.fromFloat((mP2-mP1) / pixelSize);

        //renderCoors.mulEntries(mFuzz) = pixel coords
        mFuzz = Vector2f.fromInt(mConcreteSize) / mRendererSize;

        mSrcImage = new FloatCanvas(mConcreteSize);
        mDstImage = new FloatCanvas(mConcreteSize);

        //xxx: letzte beiden parameter falsch
        mRenderer.initRender(pixelSize, validFrom, validTo);

        RGBColor col;
        for (int y = 0; y < mConcreteSize.y; y++) {
            for (int x = 0; x < mConcreteSize.x; x++) {
                mRenderer.render(Vector2f(x, y) / mFuzz, col);
                mSrcImage.drawPixel(Vector2i(x, y), col);
            }
        }

        mRenderer.terminateRender();

        FilterBitmap(mDstImage, mSrcImage, mFilterKernel, mFilterWidth,
            mFilterHeight);*/
    }

    public void terminateRender() {
        delete mSrcImage;
        delete mDstImage;
        mSrcImage = mDstImage = null;
    }

    public Vector2f renderSize() {
        //keep seitenverhaeltnis
        Vector2f bla;
        //return mRenderer.renderSize;
        return bla;
    }

    public void render(Vector2f pos, out RGBColor col) {
        Vector2i concretePos = Vector2i.fromFloat(pos.mulEntries(mFuzz));

        assert(concretePos.x >= 0);
        assert(concretePos.y >= 0);
        assert(concretePos.x < mConcreteSize.x);
        assert(concretePos.y < mConcreteSize.y);

        mDstImage.getPixelColor(concretePos, col);
    }

    //size of box = square*2+1
    private void renderFilter(FilterFunction f, int sidelen) {
        int square = sidelen*2+1;
        mFilterKernel = new float[square*square];
        int s2 = square / 2;
        float energy = 0;
        //[-1,1]
        for (int x = 0; x < square; x++) {
            for (int y = 0; y < square; y++) {
                float fx = fabs(cast(float)(s2 - x)/(s2+0.5f));
                float fy = fabs(cast(float)(s2 - y)/(s2+0.5f));
                float val = f.filter(1.0f-fx, 1.0f-fy);
                energy += val;
                mFilterKernel[y*square+x] = val;
            }
        }
        for (int n = 0; n < square*square; n++) {
            mFilterKernel[n] /= energy;
        }
        mFilterWidth = square;
        mFilterHeight = square;
    }

    void setDefaultFilter(DefaultFilter filter, int side) {
        delete mFilterKernel;
        mFilterKernel = null;
        FilterFunction func = null;
        switch (filter) {
            case DefaultFilter.MITCHELL:
                func = new FilterMitchell();
                break;
            case DefaultFilter.GAUSS:
                func = new FilterGauss();
                break;
            case DefaultFilter.TRIANGLE:
                func = new FilterTriangle();
                break;
            case DefaultFilter.BOX:
                func = new FilterBox();
                break;
            case DefaultFilter.NONE:
            default:
                break;
        }

        char[] name;

        if (func) {
            renderFilter(func, side);
            name = func.name();
            delete func;
            func = null;
        } else {
            mFilterKernel = new float[1];
            mFilterKernel[0] = 1.0f;
            mFilterWidth = 1;
            mFilterHeight = 1;
            name = "none";
        }

        mFilterName = name;

        writefln("Filter Kernel (%s): \n", name);
        for (int y = 0; y < mFilterHeight; y++) {
            writefln(" ");
            for (int x = 0; x < mFilterWidth; x++) {
                writefln("%5.2f ", mFilterKernel[y*mFilterWidth+x]);
            }
            writefln("\n");
        }
        writefln(".\n");
    }
}

private void FilterBitmap(AbstractCanvas dest, AbstractCanvas source,
    float[] filter, int filterW, int filterH)
{
    int w = source.width;
    int h = source.height;

    int filterX0 = filterW/2;
    int filterY0 = filterW/2;

    for (int y = filterY0; y < h - filterH + filterY0; y++) {
        for (int x = filterX0; x < w - filterW + filterX0; x++) {
            RGBColor destcol;
            for (int sx = 0; sx < filterW; sx++) {
                for (int sy = 0; sy < filterH; sy++) {
                    float tr, tg, tb;
                    RGBColor col;
                    source.getPixelColor(Vector2i(x-filterX0+sx, y-filterY0+sy),
                        col);
                    destcol.r += col.r*filter[sy*filterW+sx];
                    destcol.g += col.g*filter[sy*filterW+sx];
                    destcol.b += col.b*filter[sy*filterW+sx];
                }
            }
            dest.drawPixel(Vector2i(x, y), destcol);
        }
    }

    //clear out borders
    RGBColor backcol;
    for (int y = 0; y < h; y++) {
        int x = 0;
        while (x < w) {
            if (y >= filterY0 && y < h-filterY0 && x >= filterX0 && x < w-filterX0) {
                x = w-filterX0;
            }
            dest.drawPixel(Vector2i(x, y), backcol);
            x++;
        }
    }
}


private class FilterBox : FilterFunction {
    float filter(float n) {
        return 1.0f;
    }
    char[] name() {return "box";}
}

private class FilterTriangle : FilterFunction {
    float filter(float n) {
        return n;
    }
    char[] name() {return "triangle";}
}

private class FilterGauss : FilterFunction {
    float filter(float n) {
        return exp(n*n);
    }
    char[] name() {return "gauss";}
}

private class FilterMitchell : FilterFunction {
    float filter(float n) {
        double t = 1.0f - n;

        //geklaut von KDE Krita

        const double B=2.0/3.0;
        const double C=1.0/3.0;
        double tt;

        tt = t * t;
        if(t < 0) t = -t;
        if(t < 1.0) {
                t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt)) + ((-18.0 + 12.0 * B + 6.0 * C) * tt) + (6.0 - 2 * B));
                return(t / 6.0);
        } else if(t < 2.0) {
                t = (((-1.0 * B - 6.0 * C) * (t * tt)) + ((6.0 * B + 30.0 * C) * tt) + ((-12.0 * B - 48.0 * C) * t) + (8.0 * B + 24 * C));
                return(t / 6.0);
                }
        return(0.0);
        return 1.0f;
    }
    char[] name() {return "mitchell";}
}

